package com.earthea.scaffold.common.aop.aspect;

import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.earthea.scaffold.common.aop.annotation.ControllerLog;
import com.earthea.scaffold.common.entity.BaseRsp;
import com.earthea.scaffold.common.exception.ServiceException;
import com.earthea.scaffold.common.util.SecurityUtils;
import com.earthea.scaffold.common.util.ServletUtils;
import com.earthea.scaffold.domain.LoginUser;
import com.earthea.scaffold.domain.OperLog;
import com.earthea.scaffold.system.service.OperLogService;
import com.earthea.common.constant.NumberConstant;
import com.earthea.common.util.JacksonUtils;
import com.google.common.net.HttpHeaders;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * 操作日志记录处理
 */
@Aspect
@Component
public class ControllerLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger("operLog");

    @Autowired
    private OperLogService operLogService;

    @Around(value = "@annotation(controllerLog)")
    public Object around(ProceedingJoinPoint joinPoint, ControllerLog controllerLog) {
        long start = System.currentTimeMillis();
        OperLog operLog = new OperLog();
        operLog.setOperateType(controllerLog.operateType());
        operLog.setTitle(controllerLog.title());
        operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
        operLog.setRequestUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
        getExt(operLog);
        if (controllerLog.isSaveRequestData()) {
            // 是否需要保存request，参数和值
            setRequestValue(joinPoint, operLog);
        }
        try {
            LOGGER.info(operLog.requestLog(SecurityUtils.getLoginUserName()));
            BaseRsp response = (BaseRsp) joinPoint.proceed();
            operLog.setStatus(NumberConstant.IntegerStatus.SUCCESS);
            // 是否需要保存response，参数和值
            if (controllerLog.isSaveResponseData() && Objects.nonNull(response)) {
                if (response.isFailed()) {
                    operLog.setStatus(NumberConstant.IntegerStatus.FAILED);
                }
                operLog.setResponse(StringUtils.substring(JacksonUtils.toJsonAnonymize(response), 0, 2000));
            }
            LOGGER.info(operLog.responseLog(SecurityUtils.getLoginUserName()));
            return response;
        } catch (Throwable e) {
            operLog.setStatus(NumberConstant.IntegerStatus.FAILED);
            final String str = e.getMessage();
            operLog.setErrMsg(StringUtils.substring(str, 0, 2000));
            LOGGER.error(operLog.errLog(SecurityUtils.getLoginUserName()), e);
            throw new ServiceException(e);
        } finally {
            operLog.setCreateBy(Optional.ofNullable(SecurityUtils.getLoginUser()).map(LoginUser::getUserId).orElse(-1L));
            // 设置消耗时间
            operLog.setCostTime(System.currentTimeMillis() - start);
            // 保存数据库
            operLogService.insert(operLog);
        }
    }

    public static void getExt(OperLog operLog) {
        // 获取客户端IP
        operLog.setIp(ServletUtils.getIpAddr());
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader(HttpHeaders.USER_AGENT));
        // 获取客户端操作系统
        operLog.setOs(userAgent.getOs().getName());
        // 获取客户端浏览器
        operLog.setBrowser(userAgent.getBrowser().getName());
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog 操作日志
     */
    private void setRequestValue(ProceedingJoinPoint joinPoint, OperLog operLog) {
        Object[] paramsArray = joinPoint.getArgs();
        StringBuilder params = new StringBuilder();
        if (ArrayUtils.isNotEmpty(paramsArray)) {
            for (Object param : paramsArray) {
                if (Objects.nonNull(param) && !isFilterObject(param)) {
                    String jsonObj = JacksonUtils.toJsonAnonymize(param);
                    params.append(jsonObj).append("\n");
                }
            }
        }
        operLog.setRequest(StringUtils.substring(params.toString().trim(), 0, 2000));
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    private boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult;
    }
}
